13.2 Serialisierung mit »BinaryFormatter«  
Ein Objekt unter .NET serialisierbar zu machen, ist geradezu genial einfach gelöst. Allerdings muss man sich schon bei der Entwicklung einer Klasse darüber klar sein, dass Objekte der Klasse dem Serialisierungsprozess zugeführt werden sollen. Dazu wird die Klasse nur mit dem Attribut Serializable markiert:
| <Serializable()> Public Class ClassA
|
| ...
|
| End Class
|
Fehlt das Attribut, wird die Ausnahme SerializationException ausgelöst. Alle Felder der Klasse ClassA, unabhängig davon, ob sie privat oder öffentlich deklariert sind, werden damit von der Serialisierung erfasst. Es gibt aber auch eine Einschränkung:
|
Lokale Variablen und statische Klassendaten nehmen nicht an einem Serialisierungsprozess teil.
|
Wir wollen nun die Klassendefinition komplettieren, um anhand eines einfachen Beispiels zu sehen, wie die Serialisierung angestoßen und später das serialisierte Objekt rekonstruiert wird. Dazu implementieren wir in der Klasse ClassA ein privates und ein öffentliches Feld, die beide über einen parametrisierten Konstruktor initialisiert werden.
| <Serializable()> Class ClassA
|
| Public intVar As Integer
|
| Private strName As String
|
| Public Sub New(ByVal x As Integer, ByVal str As String)
|
| intVar = x
|
| strName = str
|
| End Sub
|
| Public Property Name() As String
|
| Get
|
| Return strName
|
| End Get
|
| Set(ByVal value As String)
|
| strName = value
|
| End Set
|
| End Property
|
| End Class
|
Wird die Serialisierung angestoßen, greift sich der Prozess den Inhalt von intVar und strName und speichert ihn entweder in einer Datei, im Netzwerk oder in einer Datenbank.
Der Code, der ein Objekt vom Typ der Klasse ClassA serialisiert, könnte folgendermaßen aussehen:
| Imports System.Runtime.Serialization.Formatters.Binary
|
| ...
|
| Dim obj As ClassA = New ClassA(310, "Peter")
|
| Dim myStream As FileStream
|
| myStream = New FileStream("C:\MyObject.dat", FileMode.Create)
|
| Dim binFormatter As BinaryFormatter = New BinaryFormatter()
|
| binFormatter.Serialize(myStream, obj)
|
| myStream.Close()
|
Im Code wird ein FileStream generiert, der die binäre Datei MyObject.dat anlegt oder, falls eine Datei dieses Namens bereits existiert, die alte überschreibt. Anschließend erzeugen wir ein Objekt vom Typ BinaryFormatter, dessen Methode Serialize wir unter Übergabe des Stream-Objekts und der zu serialisierenden Objektreferenz aufrufen.
Die Deserialisierung
Die Deserialisierung des gespeicherten Objekts ist genauso einfach. Beachtet werden muss dabei nur, dass der Rückgabewert vom Typ Object ist und deshalb noch in den richtigen Typ konvertiert werden muss.
| Dim oldObj As ClassA
|
| Dim binFormatter As New BinaryFormatter
|
| Dim fs As FileStream = New FileStream("C:\MyObject.dat", FileMode.Open)
|
| oldObj = binFormatter.Deserialize(fs)
|
Fassen wir nun den Code in einem Beispielprogramm zusammen. Serialisierung und Deserialisierung werden in je einer eigenen Methode behandelt, die aus Main heraus aufgerufen wird. Nach der Serialisierung des Objekts obj wird der neuen Objektvariablen oldObj der Rückgabewert der Deserialisierung zugewiesen. Zum Schluss werden die rekonstruierten Objektdaten an der Konsole ausgegeben.
| ' ----------------------------------------------------------
|
| ' Beispiel: ...\Kapitel 13\BinaryFormatter
|
| ' ----------------------------------------------------------
|
| Imports System.IO
|
| Imports System.Runtime.Serialization.Formatters.Binary
|
| Module Module1
|
| Dim binFormatter As _
|
| Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
| Dim myStream As FileStream
|
| Sub Main()
|
| binFormatter = _
|
| New Runtime.Serialization.Formatters.Binary.BinaryFormatter()
|
| Dim obj As ClassA = New ClassA(310, "Peter")
|
| SerializeObject(obj)
|
| Dim oldObj As ClassA = DeserializeObject()
|
| Console.WriteLine("Ergebnis der Deserialisierung:")
|
| Console.WriteLine(oldObj.intVar)
|
| Console.WriteLine(oldObj.Name)
|
| End Sub
|
| ' Objekt serialisieren
|
| Public Sub SerializeObject(ByVal obj As Object)
|
| myStream = New FileStream("C:\MyObject.dat", _
|
| FileMode.Create)
|
| binFormatter.Serialize(myStream, obj)
|
| myStream.Close()
|
| End Sub
|
| ' deserialisieren
|
| Public Function DeserializeObject() As ClassA
|
| Dim fs As FileStream = _
|
| New FileStream("C:\MyObject.dat", FileMode.Open)
|
| Return binFormatter.Deserialize(fs)
|
| End Function
|
| End Module
|
| <Serializable()> Public Class ClassA
|
| Public intVar As Integer
|
| Private strName As String
|
| Public Sub New(ByVal x As Integer, ByVal str As String)
|
| intVar = x
|
| strName = str
|
| End Sub
|
| Public Property Name() As String
|
| Get
|
| Return strName
|
| End Get
|
| Set(ByVal value As String)
|
| strName = value
|
| End Set
|
| End Property
|
| End Class
|
Die Ausgabe des Programms lautet:
Serialisierung mehrerer Objekte
Natürlich können mit einem Serialisierungsprozess auch beliebig viele, auch typunterschiedliche Objekte serialisiert werden. Dazu muss man für jedes Objekt Serialize auf demselben Stream-Objekt aufrufen. Die formatierten Daten werden entsprechend der Aufrufreihenfolge serialisiert.
Die Deserialisierung erfolgt in gleicher Weise: Es wird auf demselben Stream-Objekt so lange Deserialize aufgerufen, bis der Datenstrom versiegt. Das Lesen über das Ende des Datenstroms hinaus hat eine Ausnahme zur Folge. Allerdings muss dabei die Reihenfolge beachtet werden, in der die Objekte serialisiert worden sind, denn die Deserialisierung mehrerer Objekte folgt dem FIFO-Prinzip: Das zuerst serialisierte Objekt muss auch als erstes wieder deserialisiert werden.
Für jedes einzelne Objekt Serialize aufzurufen, kann sehr arbeitsaufwändig sein. Außerdem muss die Anzahl der zu serialisierenden Objekte bekannt sein. Ist die Anzahl nicht vorhersehbar, muss ein anderer Weg beschritten werden. Es bietet sich dann an, alle Objekte in einer Objektauflistung zu verwalten und mit einem einzigen Serialize-Aufruf die gesamte Collection in den Datenstrom zu schreiben. Im folgenden Beispielprogramm wird das an vier Objekten unterschiedlichen Typs demonstriert. Sehen wir uns jedoch zuerst den Programmcode an.
| ' ----------------------------------------------------------
|
| ' Beispiel ...\Kapitel 13\CollectionSerialization
|
| ' ----------------------------------------------------------
|
| Imports System.IO
|
| Imports System.Runtime.Serialization.Formatters.Binary
|
| Imports System.Runtime.Serialization
|
| Module Module1
|
| Sub Main()
|
| Dim arrList As New ArrayList
|
| Dim obj1 As ClassA = New ClassA(2334, "Freddy")
|
| Dim obj2 As ClassA = New ClassA(13, "Beate")
|
| Dim pers1 As Person = New Person("Tollsoft")
|
| Dim pers2 As Person = New Person("Microsoft")
|
| ' Objekte einer ArrayList übergeben
|
| arrList.Add(obj1)
|
| arrList.Add(obj2)
|
| arrList.Add(pers1)
|
| arrList.Add(pers2)
|
| ' ArrayList-Objekt serialisieren
|
| SaveObject(arrList)
|
| Dim newArrList As New ArrayList
|
| ' Deserialisieren und die Objektdaten anzeigen
|
| GetObject(newArrList)
|
| Dim obj As Object
|
| For Each obj In newArrList
|
| If (TypeOf obj Is ClassA) Then
|
| Console.Write("{0}/", obj.intVar)
|
| Console.WriteLine(obj.Name)
|
| ElseIf (TypeOf obj Is Person) Then
|
| Console.WriteLine(obj.Name)
|
| End If
|
| Next
|
| Console.ReadLine()
|
| End Sub
|
| ' ArrayList-Objekt speichern
|
| Public Sub SaveObject(ByVal arr As ArrayList)
|
| Dim fs As FileStream = _
|
| New FileStream("C:\Objektliste.ifn", FileMode.Create)
|
| Dim binFormatter As BinaryFormatter = New BinaryFormatter()
|
| binFormatter.Serialize(fs, arr)
|
| fs.Close()
|
| End Sub
|
| ' Datei lesen und das ArrayList-Objekt in den Referenzpa-rameter
|
| ' schreiben – der Rückgabewert gibt Aufschluss über den
|
| ' Erfolg der Deserialisierung
|
| Public Sub GetObject(ByRef arr As ArrayList)
|
| Dim fs As FileStream = _
|
| New FileStream("C:\Objektliste.ifn", FileMode.Open)
|
| Try
|
| Dim binFormatter As BinaryFormatter = _
|
| New BinaryFormatter
|
| arr = binFormatter.Deserialize(fs)
|
| Catch e As SerializationException
|
| ' die Datei kann nicht deserialisiert werden
|
| Console.WriteLine(e.Message)
|
| Catch e As IOException
|
| ' ein Fehler ist beim Versuch des Öffnens der Datei aufgetreten
|
| Console.WriteLine(e.Message)
|
| End Try
|
| End Sub
|
| End Module
|
| ' serialisierbare Klassen
|
| <Serializable()> Class ClassA
|
| public intVar as Integer
|
| Private strName As String
|
| Public Sub New(ByVal x As Integer, ByVal str As String)
|
| intVar = x
|
| strName = str
|
| End Sub
|
| Public Property Name() As String
|
| Get
|
| Return strName
|
| End Get
|
| Set(ByVal value As String)
|
| strName = value
|
| End Set
|
| End Property
|
| End Class
|
| <Serializable()> Class Person
|
| Public Name As String
|
| Public Sub New(ByVal str As String)
|
| Name = str
|
| End Sub
|
| End Class
|
Als Typen sind die beiden Klassen Person und ClassA mit je einem Konstruktor definiert, dem die Werte die Initialisierungswerte übergeben werden. Um sicherzustellen, dass Objekte der beiden Klassen serialisierbar sind, ist den Klassendefinitionen das Attribut Serializable angeheftet.
Der benutzerdefinierten Methode SaveObject kommt die Aufgabe der Objektserialisierung zu. Sie empfängt per Definition im Parameter arr die Referenz auf ein Objekt vom Typ ArrayList, die ebenfalls mit dem Attribut Serializable gekennzeichnet ist.
Die Entscheidung für eine Collection hat zwei Vorteile: Wir brauchen nicht jedes Mitgliedsobjekt der Auflistung einzeln zu serialisieren, sondern können mit einem einzigen Aufruf von Serialize unter Übergabe der ArrayList-Referenz automatisch jedes Objekt in den Datenstrom schreiben. Außerdem kann diese Collection unterschiedliche Typen verwalten.
| binFormatter.Serialize(fs, arr)
|
GetObject liefert die Referenz auf das deserialisierte ArrayList-Objekt über den Parameter arr an den Aufrufer zurück, der seinerseits in der Pflicht steht, die Daten der einzelnen Listenobjekte abzufragen. Beachten Sie, dass nur ein Deserialize-Aufruf notwendig ist, um die Daten aller von der Collection verwalteten Objekte wieder zu erhalten.
Um SaveObject und GetObject zu testen, werden in der Routine Main insgesamt vier Objekte erzeugt – jeweils zwei von Person und ClassA. Diese Objekte werden anschließend zu Elementen des ArrayList-Objekts arrList.
Um uns vom Erfolg zu überzeugen, wird ein zweites Objekt vom Typ ArrayList erzeugt, dem die deserialisierten Objekte zugewiesen werden. Die Auflistung wird in einer For Each-Schleife Element für Element durchlaufen und der Typ der aktuellen Referenz mit dem TypeOf-Is-Operator ermittelt.
Daten als nichtserialisierbar kennzeichnen
Mit dem Attribut Serializable werden alle Instanzfelder einer Klasse serialisiert. Das mag im Einzelfall aber nicht immer wünschenswert sein. Eigenschaften, die der Serialisierungsprozess nicht erfassen soll, können durch das Setzen des Attributs NonSerialized vor der Deklaration ausgeschlossen werden.
| <Serializable()> Class ClassA
|
| Public intVar As Integer
|
| ' das Feld strProp wird nicht serialisiert
|
| <NonSerialized()> Public strProps As String
|
| ...
|
| End Class
|
Das Codefragment enthält die Klassendefinition der Felder intVar und strProp. Beide würden normalerweise während der Serialisierung abgegriffen. Der Deklaration der Instanzvariablen strProp ist allerdings NonSerialized markiert und entzieht strProp dem Serialisierungsprozess.
Serialisierung in einer abgeleiteten Klasse
Das Serializable-Attribut wird nicht vererbt. Wenn Sie also eine serialisierbare Klasse ClassA entwickeln und daraus die Klasse ClassB ableiten, muss das Attribut selbst dann mit ClassB verknüpft werden, wenn nur die aus ClassA geerbten Mitglieder serialisiert werden sollen. Ansonsten gilt die Subklasse als nicht serialisierbar.
Sollen die Felder der Klasse ClassB, die aus ClassA abgeleitet ist, serialisiert werden, muss auch die Basisklasse mit dem Serializable-Attribut verknüpft sein.
|